#pragma once
#include <Windows.h>
#include <wincodec.h>
#include <DWrite.h>
#include <DXGIType.h>
#include <cmath>
#include <vector>
#include <string>
#include <D2D1.h>
#include <d2d1_3.h>
#include <mutex>
#include "nanosvg.h"
#include "AutoFree.h"
#include "Convert.h"
#include "D2dFactory.h"

class D2dFont
{
public:
    IDWriteTextFormat *_d2d_obj;
    wchar_t FontName[MAX_PATH];
    float FontSize;
    D2dFont(const wchar_t *fontFamilyName, float _fontSize)
    {
        Init(_fontSize, fontFamilyName);
    }
    D2dFont(std::wstring &fontFamilyName, float _fontSize)
    {
        Init(_fontSize, fontFamilyName.c_str());
    }
    void Init(float _fontSize, const wchar_t *fontFamilyName)
    {
        FontSize = _fontSize;
        wcscpy_s(FontName, fontFamilyName);
        _DWriteFactory->CreateTextFormat(
            this->FontName,
            NULL,
            DWRITE_FONT_WEIGHT_NORMAL,
            DWRITE_FONT_STYLE_NORMAL,
            DWRITE_FONT_STRETCH_NORMAL,
            this->FontSize,
            L"",
            &_d2d_obj);
    }
    ~D2dFont()
    {
        if (this->_d2d_obj)
            this->_d2d_obj->Release();
    }
    static std::vector<std::wstring> GetFonts()
    {
        std::vector<std::wstring> result = std::vector<std::wstring>();
        IDWriteFontCollection *pFontCollection = NULL;
        auto hr = _DWriteFactory->GetSystemFontCollection(&pFontCollection);
        UINT32 familyCount = 0;
        if (SUCCEEDED(hr))
        {
            familyCount = pFontCollection->GetFontFamilyCount();
            for (UINT32 i = 0; i < familyCount; ++i)
            {
                IDWriteFontFamily *pFontFamily = NULL;
                if (SUCCEEDED(hr))
                {
                    hr = pFontCollection->GetFontFamily(i, &pFontFamily);
                    IDWriteLocalizedStrings *pFamilyNames = NULL;
                    if (SUCCEEDED(hr))
                    {
                        hr = pFontFamily->GetFamilyNames(&pFamilyNames);
                        UINT32 index = 0;
                        BOOL exists = false;

                        wchar_t localeName[LOCALE_NAME_MAX_LENGTH];

                        if (SUCCEEDED(hr))
                        {
                            int defaultLocaleSuccess = GetUserDefaultLocaleName(localeName, LOCALE_NAME_MAX_LENGTH);
                            if (defaultLocaleSuccess)
                            {
                                hr = pFamilyNames->FindLocaleName(localeName, &index, &exists);
                            }
                            if (SUCCEEDED(hr) && !exists)
                            {
                                hr = pFamilyNames->FindLocaleName(L"en-us", &index, &exists);
                            }
                        }
                        if (!exists)
                            index = 0;
                        UINT32 length = 0;
                        if (SUCCEEDED(hr))
                        {
                            hr = pFamilyNames->GetStringLength(index, &length);
                        }
                        AutoFree <wchar_t> name(length + 1);
                        if (name == NULL)
                        {
                            hr = E_OUTOFMEMORY;
                        }
                        if (SUCCEEDED(hr))
                        {
                            hr = pFamilyNames->GetString(index, name, length + 1);
                            result.push_back(std::wstring(name));
                        }
                    }
                }
            }
        }
        return result;
    }
    D2D1_SIZE_F GetTextSize(std::wstring str, float w = 1000000, float h = 1000000)
    {
        D2D1_SIZE_F minSize = {0, 0};
        IDWriteTextLayout *textLayout = 0;
        HRESULT hr = _DWriteFactory->CreateTextLayout(
            str.c_str(),
            str.size(),
            this->_d2d_obj,
            w,
            h,
            &textLayout);

        if (SUCCEEDED(hr))
        {
            DWRITE_TEXT_METRICS metrics;
            hr = textLayout->GetMetrics(&metrics);
            textLayout->Release();
            if (SUCCEEDED(hr))
            {
                minSize = D2D1::Size((float)ceil(metrics.widthIncludingTrailingWhitespace), (float)ceil(metrics.height));
                return minSize;
            }
        }
        return {0, 0};
    }
    D2D1_SIZE_F GetTextSize(std::string str, float w = 1000000, float h = 1000000)
    {
        if (str.size() == 0)
            return {0, 0};
        AutoFree<wchar_t> tchar = Convert::A2W(str);
        IDWriteTextLayout *textLayout = 0;
        HRESULT hr = _DWriteFactory->CreateTextLayout(
            (LPWCH)tchar,
            wcsnlen_s(tchar, str.length() * sizeof(wchar_t)),
            this->_d2d_obj,
            w,
            h,
            &textLayout);

        if (SUCCEEDED(hr))
        {
            DWRITE_TEXT_METRICS metrics;
            hr = textLayout->GetMetrics(&metrics);
            textLayout->Release();
            if (SUCCEEDED(hr))
            {
                D2D1_SIZE_F minSize = D2D1::Size((float)ceil(metrics.widthIncludingTrailingWhitespace), (float)ceil(metrics.height));
                return minSize;
            }
        }
        return {0, 0};
    }
    D2D1_SIZE_F GetTextSize(wchar_t c)
    {
        D2D1_SIZE_F minSize = {0, 0};
        IDWriteTextLayout *textLayout = 0;
        HRESULT hr = _DWriteFactory->CreateTextLayout(
            &c,
            1,
            this->_d2d_obj,
            10000000,
            10000000,
            &textLayout);
        if (SUCCEEDED(hr))
        {
            DWRITE_TEXT_METRICS metrics;
            hr = textLayout->GetMetrics(&metrics);
            textLayout->Release();
            if (SUCCEEDED(hr))
            {
                minSize = D2D1::Size((float)ceil(metrics.widthIncludingTrailingWhitespace), (float)ceil(metrics.height));
                return minSize;
            }
        }
        return {0, 0};
    }
    int HitTestTextPosition(std::wstring str, float x, float y)
    {
        if (str.size() == 0)
            return -1;
        IDWriteTextLayout *textLayout = NULL;
        HRESULT hr = _DWriteFactory->CreateTextLayout(
            str.c_str(),
            str.size(),
            this->_d2d_obj,
            10000000,
            10000000,
            &textLayout);
        if (SUCCEEDED(hr))
        {
            BOOL isTrailingHit;
            BOOL isInside;
            DWRITE_HIT_TEST_METRICS caretMetrics;
            textLayout->HitTestPoint(
                x,
                y,
                &isTrailingHit,
                &isInside,
                &caretMetrics);
            textLayout->Release();
            return isTrailingHit ? caretMetrics.textPosition + 1 : caretMetrics.textPosition;
        }
        return -1;
    }
    int HitTestTextPosition(std::wstring str, float width, float height, float x, float y)
    {
        if (str.size() == 0)
            return -1;
        IDWriteTextLayout *textLayout = NULL;
        HRESULT hr = _DWriteFactory->CreateTextLayout(
            str.c_str(),
            str.size(),
            this->_d2d_obj,
            width,
            height,
            &textLayout);
        if (SUCCEEDED(hr))
        {
            BOOL isTrailingHit;
            BOOL isInside;
            DWRITE_HIT_TEST_METRICS caretMetrics;
            textLayout->HitTestPoint(
                x,
                y,
                &isTrailingHit,
                &isInside,
                &caretMetrics);
            textLayout->Release();
            if (caretMetrics.width > 0.0f && x - caretMetrics.left >= caretMetrics.width * 0.5f)
            {
                caretMetrics.textPosition += 1;
            }
            return caretMetrics.textPosition;
        }
        return -1;
    }
    std::vector<DWRITE_HIT_TEST_METRICS> HitTestTextRange(std::wstring str, UINT32 start, UINT32 len)
    {
        std::vector<DWRITE_HIT_TEST_METRICS> hitTestMetrics;
        if (str.size() == 0)
            return hitTestMetrics;
        IDWriteTextLayout *textLayout = NULL;
        HRESULT hr = _DWriteFactory->CreateTextLayout(
            str.c_str(),
            str.size(),
            this->_d2d_obj,
            10000000,
            10000000,
            &textLayout);
        if (SUCCEEDED(hr))
        {
            UINT32 actualHitTestCount = 0;

            hr = textLayout->HitTestTextRange(
                start,
                len,
                0.0f, // x
                0.0f, // y
                NULL,
                0, // metrics count
                &actualHitTestCount);
            hitTestMetrics.resize(actualHitTestCount);
            UINT32 textLen = len;
            hr = textLayout->HitTestTextRange(
                start,
                len,
                0.0f, // x
                0.0f, // y
                hitTestMetrics.data(),
                hitTestMetrics.size(),
                &actualHitTestCount);
            textLayout->Release();
        }
        return hitTestMetrics;
    }
    std::vector<DWRITE_HIT_TEST_METRICS> HitTestTextRange(std::wstring str, float width, float height, UINT32 start, UINT32 len)
    {
        std::vector<DWRITE_HIT_TEST_METRICS> hitTestMetrics;
        if (str.size() == 0)
        {
            hitTestMetrics.push_back(DWRITE_HIT_TEST_METRICS{0});
            return hitTestMetrics;
        }
        IDWriteTextLayout *textLayout = NULL;
        HRESULT hr = _DWriteFactory->CreateTextLayout(
            str.c_str(),
            str.size(),
            this->_d2d_obj,
            width,
            height,
            &textLayout);
        if (SUCCEEDED(hr))
        {
            UINT32 actualHitTestCount = 0;

            hr = textLayout->HitTestTextRange(
                start,
                len,
                0.0f, // x
                0.0f, // y
                NULL,
                0, // metrics count
                &actualHitTestCount);
            hitTestMetrics.resize(actualHitTestCount);
            UINT32 textLen = len;
            hr = textLayout->HitTestTextRange(
                start,
                len,
                0.0f, // x
                0.0f, // y
                hitTestMetrics.data(),
                hitTestMetrics.size(),
                &actualHitTestCount);
            textLayout->Release();
        }
        if (hitTestMetrics.size() == 0)
        {
            hitTestMetrics.push_back(DWRITE_HIT_TEST_METRICS{0});
        }
        return hitTestMetrics;
    }
    int HitTestTextPosition(IDWriteTextLayout *textLayout, float x, float y)
    {
        if (textLayout)
        {
            BOOL isTrailingHit;
            BOOL isInside;
            DWRITE_HIT_TEST_METRICS caretMetrics;
            textLayout->HitTestPoint(
                x,
                y,
                &isTrailingHit,
                &isInside,
                &caretMetrics);
            return isTrailingHit ? caretMetrics.textPosition + 1 : caretMetrics.textPosition;
        }
        return -1;
    }
    int HitTestTextPosition(IDWriteTextLayout *textLayout, float width, float height, float x, float y)
    {
        if (textLayout)
        {
            BOOL isTrailingHit;
            BOOL isInside;
            DWRITE_HIT_TEST_METRICS caretMetrics;
            textLayout->HitTestPoint(
                x,
                y,
                &isTrailingHit,
                &isInside,
                &caretMetrics);
            return caretMetrics.textPosition;
        }
        return -1;
    }
    std::vector<DWRITE_HIT_TEST_METRICS> HitTestTextRange(IDWriteTextLayout *textLayout, UINT32 start, UINT32 len)
    {
        std::vector<DWRITE_HIT_TEST_METRICS> hitTestMetrics;
        UINT32 actualHitTestCount = 0;
        if (textLayout)
        {
            auto hr = textLayout->HitTestTextRange(
                start,
                len,
                0.0f, // x
                0.0f, // y
                NULL,
                0, // metrics count
                &actualHitTestCount);
            hitTestMetrics.resize(actualHitTestCount);
            UINT32 textLen = len;
            hr = textLayout->HitTestTextRange(
                start,
                len,
                0.0f, // x
                0.0f, // y
                hitTestMetrics.data(),
                hitTestMetrics.size(),
                &actualHitTestCount);
        }
        return hitTestMetrics;
    }
    std::vector<DWRITE_HIT_TEST_METRICS> HitTestTextRange(IDWriteTextLayout *textLayout, float width, float height, UINT32 start, UINT32 len)
    {
        std::vector<DWRITE_HIT_TEST_METRICS> hitTestMetrics;
        if (textLayout)
        {
            UINT32 actualHitTestCount = 0;
            auto hr = textLayout->HitTestTextRange(
                start,
                len,
                0.0f, // x
                0.0f, // y
                NULL,
                0, // metrics count
                &actualHitTestCount);
            hitTestMetrics.resize(actualHitTestCount);
            UINT32 textLen = len;
            hr = textLayout->HitTestTextRange(
                start,
                len,
                0.0f, // x
                0.0f, // y
                hitTestMetrics.data(),
                hitTestMetrics.size(),
                &actualHitTestCount);
        }
        return hitTestMetrics;
    }
};